/*
 * Package il.ac.biu.cs.grossmm.impl.activeData
 * File ConversionProcessor.java
 * 
 * This is a part of presence service framework prototype. 
 * See javadoc for more information.
 *  
 * Copyright (C) 2006 Grossmann Mark
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.

 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package il.ac.biu.cs.grossmm.impl.activeData;

import static il.ac.biu.cs.grossmm.impl.keys.KeyOperations.transform;
import static il.ac.biu.cs.grossmm.impl.keys.PatternMatcher.matches;
import il.ac.biu.cs.grossmm.api.OperationFailedException;
import il.ac.biu.cs.grossmm.api.data.NodeType;
import il.ac.biu.cs.grossmm.api.flow.ConditionalConverter;
import il.ac.biu.cs.grossmm.api.flow.Converter;
import il.ac.biu.cs.grossmm.api.flow.ConverterProxy;
import il.ac.biu.cs.grossmm.api.flow.NodeInspector;
import il.ac.biu.cs.grossmm.api.flow.NodeManipulator;
import il.ac.biu.cs.grossmm.api.flow.NotificationProvider;
import il.ac.biu.cs.grossmm.api.flow.Subscriber;
import il.ac.biu.cs.grossmm.api.flow.Track;
import il.ac.biu.cs.grossmm.api.keys.Key;
import il.ac.biu.cs.grossmm.api.keys.KeyPattern;
import il.ac.biu.cs.grossmm.impl.keys.PatternMatcher;

import java.util.HashMap;
import java.util.Map;

/**
 * A wrapper for a data converter which itself is a data converter (by means
 * of delegation) and provides additional meta information.  
 *
 * @param <Nsrc> source node type 
 * @param <Ndst> destination node type
 */
public class ConversionProcessor<Nsrc, Ndst> implements Converter<Nsrc, Ndst> {

	/**
	 * The wrapped converter
	 */
	Converter<Nsrc, Ndst> converter;

	/**
	 * Source data layer
	 */
	DataLayer<Nsrc> srcLayer;

	/**
	 * Destination data layer
	 */
	DataLayer<Ndst> dstLayer;

	/**
	 * Pattern matcher which filters out any data (by keys) which cannot 
	 * be converted using the wrapped converter
	 */
	PatternMatcher matcher;

	/**
	 * Set of subscriptions proxied by this conversion processor
	 * indexed by thir keys
	 */
	Map<Key, SubscriptionImpl> subscriptions;

	/**
	 * Creates a new conversion processor, based on a given converter,
	 * source and destination layers.
	 * @param converter the data converter to be wrapped
	 * @param srcLayer source layer
	 * @param dstLayer destination layer
	 */
	public ConversionProcessor(Converter<Nsrc, Ndst> converter,
			DataLayer<Nsrc> srcLayer, DataLayer<Ndst> dstLayer) {
		this.converter = converter;
		this.srcLayer = srcLayer;
		this.dstLayer = dstLayer;
		this.subscriptions = new HashMap<Key, SubscriptionImpl>();
	}

	public double getPriority() {
		return converter.getPriority();
	}

	
	public ConverterProxy<Nsrc, Ndst> createProxy(
			il.ac.biu.cs.grossmm.api.data.Root<Nsrc> srcNode,
			il.ac.biu.cs.grossmm.api.data.Root<Ndst> dstNode,
			NodeInspector srcInspector, NodeManipulator dstManipulator)
			throws OperationFailedException {
		return converter.createProxy(srcNode, dstNode, srcInspector,
				dstManipulator);
	}

	/**
	 * @return
	 * @see il.ac.biu.cs.grossmm.api.flow.Converter#getDestinationType()
	 */
	public NodeType<Ndst> getDestinationType() {
		return converter.getDestinationType();
	}

	/**
	 * @return
	 * @see il.ac.biu.cs.grossmm.api.flow.Converter#getSourceType()
	 */
	public NodeType<Nsrc> getSourceType() {
		return converter.getSourceType();
	}

	public KeyPattern getSourcePattern() {
		return ((ConditionalConverter<Nsrc, Ndst>) converter)
				.getSourcePattern();
	}

	public KeyPattern getDestinationPattern() {
		return ((ConditionalConverter<Nsrc, Ndst>) converter)
				.getDestinationPattern();
	}

	public boolean isConditional() {
		return converter instanceof ConditionalConverter;
	}

	/**
	 * Transforms the key 
	 * @param key the key to be transformed
	 * @return the transformed key
	 */
	public Key transformKey(Key key) {
		if (isConditional()) {
			ConditionalConverter<Nsrc, Ndst> cc = (ConditionalConverter<Nsrc, Ndst>) converter;
			return transform(key, cc.getSourcePattern(), cc
					.getDestinationPattern());
		} else
			return key;
	}

	@SuppressWarnings("unchecked")
	SubscriptionImpl<Ndst> subscribe(Key key, ConversionProcessor[] converters,
			int index, NotificationProvider np, Subscriber<Ndst> subscriber,
			Track track) {
		assert converters[index] == this;

		// check cache for already existing destination subscription
		SubscriptionImpl<Ndst> dstSubscription = dstLayer
				.tryCached(key, subscriber);

		// return the cached subscription in the case of the cache hit
		if (dstSubscription != null) {
			assert ((Mediator) dstSubscription.provider).processor == this;
			return dstSubscription;
		}

		// cache miss, try to create a new subscription
		SubscriptionImpl<Nsrc> srcSubscription;

		// the mediator to chain the subscriptions
		Mediator<Nsrc, Ndst> mediator = new Mediator<Nsrc, Ndst>(this);

		// convert the key from the destionation to the source
		Key cKey = toSourceKey(key);

		if (converters.length == index + 1) {
			// subscribe to data output point
			srcSubscription = (SubscriptionImpl<Nsrc>) np.subscribe(mediator, cKey, track);
		} else {
			// chain with the next converter
			ConversionProcessor<?, Nsrc> nextProcessor = converters[index + 1];
			srcSubscription = nextProcessor.subscribe(cKey, converters,
					index + 1, np, mediator, track);
		}

		// was able to subscribe?
		if (srcSubscription == null)
			return null; // could not subscribe

		// create the destination subscription
		dstSubscription = new SubscriptionImpl<Ndst>(key, null, mediator);

		SubscriptionImpl<Ndst> s = dstLayer.registerSubscription(key,
				dstSubscription, subscriber);
		if (s == dstSubscription)
			mediator.setSubscriptions(srcSubscription, dstSubscription);

		return s;
	}

	/**
	 * Given the destination resource key, return the source resource key.
	 * The tansformation is an identity transofmration if the wrapped
	 * converter is unconditional conveter.
	 * 
	 * @param key the destination key
	 */
	private Key toSourceKey(Key key) {
		Key cKey;
		if (isConditional()) {
			assert matches(getDestinationPattern(), key);

			cKey = transform(key, getDestinationPattern(), getSourcePattern());
		} else
			cKey = key;

		return cKey;
	}

	@Override
	public String toString() {
		return "[" + converter.toString() + "]";
	}
}
